/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.base;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

class DefaultFeatureRegistry implements FeatureRegistry {
	private final ProxyFactory proxyFactory = new ProxyFactory();

	private final List<Object> extensions = new LinkedList<Object>();
	private final Map<Class<?>, AggregateExtension<?>> extensionsMap =
		new HashMap<Class<?>, AggregateExtension<?>>();

	private final FeatureResolver resolver = new DefaultFeatureResolver(extensions, extensionsMap);

	private final Map<Class<?>, Delegator<?>> delegatorMap = new HashMap<Class<?>, Delegator<?>>();

	public DefaultFeatureRegistry(Object ... features) {
		addFeatures(features);
		addFeatures(this);
	}

	public FeatureResolver resolver() {
		return resolver;
	}

	public void verify() {
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// FeatureRegistry methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	public void addFeatures(Object ... features) {
		for (Object feature : features) {
			this.extensions.add(feature);

			for (AggregateExtension<?> aggregateExtension : extensionsMap.values()) {
				aggregateExtension.addIfMatches(feature);
			}
		}
	}

	public <T> T add(Class<T> featureType, T defaultImplementation) {
		if (!featureType.isInterface()) {
			throw new FeaturesMustBeInterfaces(featureType);
		}

		DelegatingInvocationHandler<T> delegatingInvocationHandler =
			new DelegatingInvocationHandler<T>(defaultImplementation);

		delegatorMap.put(featureType, delegatingInvocationHandler);

		return proxyFactory.create(featureType, delegatingInvocationHandler);
	}

	public <T> void migrate(Class<T> featureType, Migration<T> migrator) {
		replace(featureType, migrator.migrate(currentImplementation(featureType)));
	}
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	private <T> T currentImplementation(Class<T> featureType) {
		return delegator(featureType).delegate();
	}

	private <T> void replace(Class<T> featureType, T implementation) {
		delegator(featureType).setDelegate(implementation);
	}

	@SuppressWarnings("unchecked")
	private <T> Delegator<T> delegator(Class<T> featureType) {
		return (Delegator<T>) delegatorMap.get(featureType);
	}

	static class DelegatingInvocationHandler<T> implements InvocationHandler, Delegator<T> {
		private T delegate;

		public DelegatingInvocationHandler(T initialDelegate) {
			this.delegate = initialDelegate;
		}

		public T delegate() {
			return delegate;
		}

		public T setDelegate(T newDelegate) {
			T previousDelegate = delegate;

			delegate = newDelegate;

			return previousDelegate;
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			return method.invoke(delegate, args);
		}
	}
}
